<!-- 高亮文本输入框组件的主模板 -->
<template>
  <!-- 主容器：监听点击事件处理空白区域点击聚焦到最后一个块 -->
  <div class="highlight-input" ref="containerRef" @click="handleContainerClick" :class="{ 'is-empty': isInputEmpty }">
    <!--
      动态渲染任意数量的文本块和高亮块
      每个span元素代表一个可编辑的文本块，可以是：
      1. 普通文本块：显示普通文本，用户可以编辑
      2. 高亮块：显示高亮文本，有占位符功能
    -->
    <span v-for="(block, index) in blocks" :key="block.id" :class="[
      block.type === 'text' ? 'normal-text' : 'highlight-block',
      { 'has-content': block.type === 'highlight' && block.hasUserInput },
      { 'after-highlight': index > 0 && blocks[index - 1]?.type === 'highlight' }
    ]" contenteditable="true" :data-index="index"
      :data-placeholder="block.type === 'highlight' ? block.placeholder : undefined"
      @input="onBlockInput($event, index)" @focus="onBlockFocus(index)" @blur="onBlockBlur(index)"
      @keydown="handleKeyNavigation($event, index)" :ref="(el) => setBlockRef(index, el)" spellcheck="false">{{
        block.type === 'highlight' ? block.displayValue : block.value }}</span>

    <!-- 整体占位符：当输入框为空时显示 -->
    <div v-if="isInputEmpty" class="input-placeholder" @click="handlePlaceholderClick">
      {{ placeholder }}
    </div>
  </div>
</template>

<!-- Vue3 Composition API 脚本部分 -->
<script setup>
// 导入Vue3的响应式API和生命周期钩子
import { ref, onMounted, nextTick, watch, computed } from 'vue'

/**
 * Vue3 高亮文本输入框组件
 * 功能：支持高亮显示特定文本区域，可编辑文本内容
 * 特点：动态解析后端返回的文本，自动识别中括号内的高亮区域
 *
 * 主要特性：
 * 1. 支持任意数量的高亮区域
 * 2. 键盘导航（方向键、回车、删除）
 * 3. v-model双向绑定
 * 4. 中括号保留控制
 * 5. 可配置的回车换行行为
 * 6. 高亮区域复制功能（回车复制块到下一行）
 * 7. 整体占位符支持
 * 8. Ctrl/Shift+Enter快捷键
 * 9. 文本中间换行支持
 * 10. 高亮区域删除功能
 */

// ==================== 事件定义 ====================
// 定义组件可以发射的事件：提交、回车、内容变化、v-model更新
const emit = defineEmits(['submit', 'enter', 'change', 'update:modelValue'])

// ==================== Props定义 ====================
// 定义组件接收的属性
const props = defineProps({
  // v-model绑定的值（用户的实际数据）
  modelValue: {
    type: String,        // 字符串类型
    default: ''          // 默认为空字符串
  },
  // 组件内部的预设模板（包含中括号的模板文本）
  presetText: {
    type: String,        // 字符串类型
    default: ''          // 默认为空字符串
  },
  // 是否在最终输出中保留中括号
  keepBrackets: {
    type: Boolean,       // 布尔类型
    default: false       // 默认不保留中括号
  },
  // 是否允许回车换行
  allowLineBreak: {
    type: Boolean,       // 布尔类型
    default: false       // 默认不允许换行，保持原有行为
  },
  // 整个输入框的占位符文本（当没有任何内容时显示）
  placeholder: {
    type: String,        // 字符串类型
    default: '请输入内容...'  // 默认占位符文本
  },
  // Ctrl/Shift+Enter的行为：'line-break'表示换行，'submit'表示提交
  ctrlEnterAction: {
    type: String,        // 字符串类型
    default: 'line-break', // 默认是换行
    validator: (value) => ['line-break', 'submit'].includes(value)
  },
  // 是否启用高亮块复制功能（回车时复制高亮块到下一行）
  enableHighlightDuplication: {
    type: Boolean,       // 布尔类型
    default: true        // 默认启用高亮块复制功能
  }
})

// ==================== 响应式数据定义 ====================
/**
 * 动态块数组，存储所有文本块和高亮块的数据
 * 每个块包含以下属性：
 * - id: 唯一标识符，用于Vue的key追踪
 * - type: 块类型，'text'表示普通文本，'highlight'表示高亮区域
 * - value: 块的实际值（用户输入的内容）
 * - placeholder: 占位符文本（仅高亮块有效）
 * - displayValue: 显示值（可能是占位符或用户输入）
 * - hasUserInput: 是否有用户输入（仅高亮块有效）
 */
const blocks = ref([])

/**
 * DOM引用数组，存储每个块对应的DOM元素
 * 索引与blocks数组对应，用于：
 * - 光标定位和移动操作
 * - 焦点控制和切换
 * - 键盘导航功能
 */
const blockRefs = ref([])

/**
 * 主容器的DOM引用，用于全选等操作
 */
const containerRef = ref(null)

// ==================== 计算属性 ====================
/**
 * 判断输入框是否为空（用于显示整体placeholder）
 * 当没有任何blocks或所有blocks都为空时，认为输入框为空
 */
const isInputEmpty = computed(() => {
  if (blocks.value.length === 0) return true

  // 检查是否所有块都为空
  return blocks.value.every(block => {
    if (block.type === 'text') {
      return !block.value || block.value.trim() === ''
    } else {
      // 高亮块：没有用户输入或输入为空
      return !block.hasUserInput || !block.value || block.value.trim() === ''
    }
  })
})

// ==================== 工具函数 ====================
/**
 * 解析预设文本，生成动态块数组
 * @param {string} text - 预设文本
 * @returns {Array} 解析后的块数组
 */
function parsePresetText(text) {
  const blocks = []
  const regex = /\[([^\]]*)\]/g
  let lastIndex = 0
  let match
  let blockId = 0

  // 遍历所有匹配的中括号
  while ((match = regex.exec(text)) !== null) {
    // 添加中括号前的普通文本块（如果存在）
    if (match.index > lastIndex) {
      const textContent = text.substring(lastIndex, match.index)
      if (textContent) {
        blocks.push({
          id: `block-${blockId++}`,
          type: 'text',
          value: textContent
        })
      }
    }

    // 添加高亮块
    blocks.push({
      id: `block-${blockId++}`,
      type: 'highlight',
      placeholder: match[1], // 中括号内的内容作为占位符
      value: '',             // 用户输入的内容
      displayValue: match[1], // 显示的内容（初始为占位符）
      hasUserInput: false    // 是否有用户输入
    })

    lastIndex = regex.lastIndex
  }

  // 添加最后一段普通文本（如果存在）
  if (lastIndex < text.length) {
    const textContent = text.substring(lastIndex)
    if (textContent) {
      blocks.push({
        id: `block-${blockId++}`,
        type: 'text',
        value: textContent
      })
    }
  }

  return blocks
}

/**
 * 设置光标到元素的指定位置
 * 用于精确控制光标在可编辑元素中的位置
 * @param {HTMLElement} el - 目标DOM元素
 * @param {string} pos - 位置：'start'表示开头，'end'表示结尾
 */
function setCursorToPos(el, pos = 'start') {
  if (!el) return                           // 如果元素不存在，直接返回

  try {
    const sel = window.getSelection()       // 获取当前的选择对象
    if (!sel) return                        // 如果选择对象不存在，直接返回

    sel.removeAllRanges()                   // 清除所有现有的选择范围

    const range = document.createRange()    // 创建一个新的Range对象

    if (pos === 'start') {
      // 设置到开头
      if (el.childNodes.length > 0) {
        const firstNode = el.childNodes[0]
        if (firstNode.nodeType === Node.TEXT_NODE) {
          range.setStart(firstNode, 0)
          range.setEnd(firstNode, 0)
        } else {
          range.setStart(el, 0)
          range.setEnd(el, 0)
        }
      } else {
        range.setStart(el, 0)
        range.setEnd(el, 0)
      }
    } else {
      // 设置到结尾
      if (el.childNodes.length > 0) {
        const lastNode = el.childNodes[el.childNodes.length - 1]
        if (lastNode.nodeType === Node.TEXT_NODE) {
          const textLength = lastNode.textContent.length
          range.setStart(lastNode, textLength)
          range.setEnd(lastNode, textLength)
        } else {
          range.setStart(el, el.childNodes.length)
          range.setEnd(el, el.childNodes.length)
        }
      } else {
        const textLength = el.textContent.length
        range.setStart(el, textLength)
        range.setEnd(el, textLength)
      }
    }

    sel.addRange(range)                     // 添加新的选择范围，设置光标位置
  } catch (error) {
    // 静默处理错误，避免控制台噪音
  }
}

/**
 * 初始化时聚焦到第一个高亮区域
 * 仅在组件挂载时调用，确保光标在第一个高亮区域
 */
function focusFirstHighlight() {
  // 使用递归重试机制，确保DOM完全准备好
  const tryFocus = (attempts = 0) => {
    const maxAttempts = 10 // 最大重试次数

    // 在blocks数组中查找第一个高亮区域的索引
    const firstHighlightIndex = blocks.value.findIndex(block => block.type === 'highlight')

    if (firstHighlightIndex !== -1) {
      const targetRef = blockRefs.value[firstHighlightIndex]

      // 检查DOM元素是否存在且在文档中
      if (targetRef && document.contains(targetRef)) {
        // 直接聚焦，让 onBlockFocus 处理后续逻辑
        targetRef.focus()
        return // 成功设置，退出函数
      }
    }

    // 如果还没准备好且没有超过最大重试次数，继续重试
    if (attempts < maxAttempts) {
      setTimeout(() => tryFocus(attempts + 1), 200)
    }
  }

  // 延迟开始，确保组件完全挂载
  setTimeout(() => tryFocus(), 100)
}

/**
 * 设置光标到元素的最左侧（开头位置）
 * 这是setCursorToPos函数的简化版本，专门用于设置光标到开头
 * @param {HTMLElement} el - 目标DOM元素
 */
function setCursorToStart(el) {
  if (!el) return                           // 如果元素不存在，直接返回

  try {
    const range = document.createRange()    // 创建一个新的Range对象
    const sel = window.getSelection()      // 获取当前的选择对象

    if (!sel) return                       // 如果选择对象不存在，直接返回

    sel.removeAllRanges()                  // 清除所有现有的选择范围

    // 如果元素为空或只有空白字符，直接设置到元素开头
    if (el.childNodes.length === 0 || el.textContent.trim() === '') {
      range.setStart(el, 0)                // 设置范围开始位置
      range.setEnd(el, 0)                  // 设置范围结束位置
    } else {
      // 如果元素有内容，设置到第一个文本节点的开头
      const firstTextNode = el.childNodes[0]
      if (firstTextNode && firstTextNode.nodeType === Node.TEXT_NODE) {
        range.setStart(firstTextNode, 0)   // 设置到文本节点的开头
        range.setEnd(firstTextNode, 0)
      } else {
        range.setStart(el, 0)              // 回退到元素开头
        range.setEnd(el, 0)
      }
    }

    sel.addRange(range)                    // 添加新的选择范围
  } catch (error) {
    // 静默处理错误，避免控制台噪音
  }
}

// ==================== 动态事件处理 ====================
/**
 * 设置块的DOM引用
 * 将DOM元素存储到blockRefs数组中，用于后续的光标操作
 * @param {number} index - 块在数组中的索引位置
 * @param {HTMLElement} el - 对应的DOM元素
 */
function setBlockRef(index, el) {
  blockRefs.value[index] = el               // 将DOM元素引用存储到对应索引位置
}

/**
 * 块输入事件处理
 * 当用户在任何块中输入文本时触发，更新对应的数据状态
 * @param {Event} e - 输入事件对象
 * @param {number} index - 当前块的索引位置
 */
function onBlockInput(e, index) {
  // 获取输入的文本内容，移除换行符（因为我们不支持多行）
  const value = e.target.innerText.replace(/\n/g, '')
  const block = blocks.value[index]         // 获取对应的块数据

  // 检查块是否还存在（防止在删除操作后触发事件）
  if (!block) return

  if (block.type === 'text') {
    // 处理普通文本块：直接更新value
    block.value = value
  } else {
    // 处理高亮块：更新value、用户输入状态和显示值
    block.value = value                     // 存储用户输入的实际值
    block.hasUserInput = value.length > 0  // 标记是否有用户输入
    block.displayValue = value              // 更新显示值
  }

  emitChangeEvent()                         // 触发变化事件，更新v-model
}

/**
 * 块聚焦事件处理
 * 当用户点击或通过键盘导航到某个块时触发
 * @param {number} index - 当前聚焦块的索引位置
 */
function onBlockFocus(index) {
  const block = blocks.value[index]         // 获取对应的块数据

  // 检查块是否还存在（防止在删除操作后触发事件）
  if (!block) return

  if (block.type === 'highlight') {
    // 处理高亮块的聚焦逻辑
    // 如果没有用户输入，则全选占位符文本，方便用户直接输入替换
    if (!block.hasUserInput) {
      nextTick(() => {
        const el = blockRefs.value[index]   // 获取对应的DOM元素
        if (el) {
          const sel = window.getSelection()
          const range = document.createRange()
          if (sel) {
            range.selectNodeContents(el)
            sel.removeAllRanges()
            sel.addRange(range)
          }
        }
      })
    }
  } else {
    // 处理普通文本块的聚焦逻辑
    nextTick(() => {
      const el = blockRefs.value[index]     // 获取对应的DOM元素
      if (el) {
        // 确保光标可见，特别是在空文本块或文本开头时
        ensureCursorVisible(el)
      }
    })
  }
}

/**
 * 块失焦事件处理
 * 当用户离开某个块时触发，用于恢复占位符显示
 * @param {number} index - 失去焦点的块索引位置
 */
function onBlockBlur(index) {
  // 检查块是否还存在（防止删除后触发blur事件）
  const block = blocks.value[index]
  if (!block) return

  if (block.type === 'highlight' && !block.hasUserInput) {
    // 高亮块且没有用户输入时，恢复显示占位符
    block.displayValue = block.placeholder  // 恢复占位符文本显示
  }
}

// ==================== 键盘导航处理 ====================
/**
 * 处理键盘导航和特殊按键
 * 支持左右箭头键在块间切换，以及回车、删除等特殊按键的自定义处理
 * @param {KeyboardEvent} e - 键盘事件对象
 * @param {number} currentIndex - 当前聚焦块的索引位置
 */
function handleKeyNavigation(e, currentIndex) {
  // 检测按下的按键类型
  const isLeft = e.key === 'ArrowLeft'        // 左箭头键
  const isRight = e.key === 'ArrowRight'      // 右箭头键
  const isEnter = e.key === 'Enter'           // 回车键

  const isBackspace = e.key === 'Backspace'  // 退格键
  const isDelete = e.key === 'Delete'         // 删除键
  const isCtrlA = e.key === 'a' && (e.ctrlKey || e.metaKey)  // Ctrl+A全选
  const isCtrlC = e.key === 'c' && (e.ctrlKey || e.metaKey)  // Ctrl+C复制
  const isCtrlEnter = isEnter && (e.ctrlKey || e.metaKey) && !e.shiftKey    // 纯Ctrl+Enter
  const isShiftEnter = isEnter && e.shiftKey && !e.ctrlKey && !e.metaKey  // 纯Shift+Enter


  // 处理Ctrl+A全选 - 选择所有文本内容
  if (isCtrlA) {
    e.preventDefault()                        // 阻止默认的全选行为
    selectAllText()                           // 调用全选函数
    return
  }

  // 处理Ctrl+C复制 - 复制完整文本内容
  if (isCtrlC) {
    handleCopyText()                          // 调用复制处理函数
    return
  }

  // 处理Ctrl+Enter和Shift+Enter - 根据ctrlEnterAction属性和块类型决定行为
  if (isCtrlEnter || isShiftEnter) {


    if (props.ctrlEnterAction === 'line-break') {
      // 如果配置为换行行为，使用统一的换行处理函数
      e.preventDefault()                      // 阻止默认行为
      handleLineBreak(currentIndex, e.target) // 统一处理换行行为
      return
    } else {
      // 如果配置为提交，阻止默认行为并触发提交
      e.preventDefault()
      handleFormSubmit()                      // 触发表单提交
      return
    }
  }

  // 处理回车键 - 根据块类型和配置决定行为
  if (isEnter) {
    const currentBlock = blocks.value[currentIndex]

    if (currentBlock && currentBlock.type === 'highlight') {
      // 高亮区域的回车行为
      if (props.enableHighlightDuplication && props.allowLineBreak) {
        // 如果启用高亮块复制：使用统一的换行处理函数
        e.preventDefault()                    // 阻止默认的换行行为
        handleLineBreak(currentIndex, e.target) // 统一处理换行行为
        return
      } else if (props.allowLineBreak) {
        // 如果不启用复制但允许换行：允许块内换行
        return
      } else {
        // 如果不启用复制也不允许换行：跳转到下一个块
        e.preventDefault()                    // 阻止默认的换行行为
        handleEnterKey(currentIndex)          // 调用回车键处理函数
        return
      }
    } else if (props.allowLineBreak) {
      // 普通文本区域：如果允许换行，则允许默认的换行行为
      return
    } else {
      // 普通文本区域：如果不允许换行，跳转到下一个块
      e.preventDefault()                      // 阻止默认的换行行为
      handleEnterKey(currentIndex)            // 调用回车键处理函数
      return
    }
  }

  // 处理backspace键 - 在高亮区域有特殊的删除逻辑
  if (isBackspace) {
    handleBackspaceKey(e, currentIndex)       // 调用退格键处理函数
    return
  }

  // 处理delete键 - 在高亮区域有特殊的删除逻辑
  if (isDelete) {
    handleDeleteKey(e, currentIndex)          // 调用删除键处理函数
    return
  }

  // 处理左右箭头键导航 - 只有在光标到达边界时才切换块
  if (!isLeft && !isRight) return            // 如果不是箭头键，直接返回

  const el = e.target                         // 获取当前元素
  const sel = window.getSelection()           // 获取当前选择对象
  if (!sel || sel.rangeCount === 0) return   // 如果没有选择范围，直接返回

  // 改进的光标位置检测
  const range = sel.getRangeAt(0)             // 获取第一个选择范围
  const isAtStart = isCaretAtStart(el, range) // 使用更可靠的开头检测
  const isAtEnd = isCaretAtEnd(el, range)     // 使用更可靠的结尾检测

  if (isLeft && isAtStart && currentIndex > 0) {
    // 左箭头键且光标在开头且不是第一个块：切换到前一个块
    e.preventDefault()                      // 阻止默认的光标移动行为
    focusBlock(currentIndex - 1, 'end')     // 聚焦到前一个块的末尾
  } else if (isRight && isAtEnd && currentIndex < blocks.value.length - 1) {
    // 右箭头键且光标在结尾且不是最后一个块：切换到下一个块
    e.preventDefault()                      // 阻止默认的光标移动行为
    focusBlock(currentIndex + 1, 'start')   // 聚焦到下一个块的开头
  }
}

/**
 * 统一的换行处理函数
 * 根据当前块类型和配置决定换行行为
 * @param {number} currentIndex - 当前块的索引位置
 * @param {HTMLElement} targetElement - 目标DOM元素
 */
function handleLineBreak(currentIndex, targetElement) {
  const currentBlock = blocks.value[currentIndex]

  if (currentBlock && currentBlock.type === 'highlight' && props.enableHighlightDuplication) {
    // 高亮区域且启用复制：复制高亮块到下一行
    duplicateHighlightBlock(currentIndex)
  } else {
    // 普通文本区域或高亮区域未启用复制：插入换行符
    insertLineBreak(targetElement)
  }
}

/**
 * 复制高亮块到下一行
 * 在当前高亮块后插入换行符和相同的高亮块
 * @param {number} currentIndex - 当前高亮块的索引位置
 */
function duplicateHighlightBlock(currentIndex) {
  const currentBlock = blocks.value[currentIndex]
  if (!currentBlock || currentBlock.type !== 'highlight') return

  // 创建换行符文本块
  const lineBreakBlock = {
    id: `text_${Date.now()}_${Math.random()}`,
    type: 'text',
    value: '\n'
  }

  // 创建新的高亮块（复制当前块的结构，但清空内容）
  const newHighlightBlock = {
    id: `highlight_${Date.now()}_${Math.random()}`,
    type: 'highlight',
    placeholder: currentBlock.placeholder,
    value: '',                          // 新块开始时为空
    hasUserInput: false,                // 新块没有用户输入
    displayValue: currentBlock.placeholder  // 显示占位符
  }

  // 在当前块后插入换行符和新的高亮块
  blocks.value.splice(currentIndex + 1, 0, lineBreakBlock, newHighlightBlock)

  // 等待DOM更新后聚焦到新的高亮块
  nextTick(() => {
    // 新高亮块的索引是 currentIndex + 2（因为插入了换行符）
    const newBlockIndex = currentIndex + 2
    focusBlock(newBlockIndex, 'start')

    // 触发内容变化事件
    emitChangeEvent()
  })
}

/**
 * 处理回车键事件
 * 当allowLineBreak为false时，回车键用于在块之间跳转，到达最后一个块时触发提交
 * 当allowLineBreak为true时，此函数不会被调用，允许正常的换行行为
 * @param {number} currentIndex - 当前块的索引位置
 */
function handleEnterKey(currentIndex) {
  // 回车键的行为：跳转到下一个组件块
  if (currentIndex < blocks.value.length - 1) {
    // 如果不是最后一个块，跳到下一个组件块的开头
    focusBlock(currentIndex + 1, 'start')
  } else {
    // 如果是最后一个组件块，触发表单提交或其他自定义行为
    // 这里可以触发表单提交或其他自定义逻辑


    // 可以在这里添加自定义的回车处理逻辑
    // 例如：触发表单提交、显示提示信息等
    handleFormSubmit()                      // 调用表单提交处理函数
  }
}

/**
 * 处理backspace键事件
 * 支持高亮区域的渐进式删除：第一次清空内容，第二次删除整个区域
 * @param {KeyboardEvent} e - 键盘事件对象
 * @param {number} currentIndex - 当前块的索引位置
 */
function handleBackspaceKey(e, currentIndex) {
  const el = e.target                       // 获取当前DOM元素
  const sel = window.getSelection()         // 获取当前选择对象
  const block = blocks.value[currentIndex]  // 获取当前块数据

  // 如果是高亮区域，实现特殊的删除逻辑
  if (block.type === 'highlight') {
    // 检查是否在高亮区域的开头位置
    if (sel && sel.rangeCount > 0) {
      const range = sel.getRangeAt(0)       // 获取选择范围
      const atStart = range.startOffset === 0  // 判断光标是否在开头
      // 判断内容是否为空（空字符串或只有占位符）
      const isEmpty = el.innerText.trim() === '' ||
        el.innerText === block.placeholder
      // 判断是否有实际的用户输入内容
      const hasContent = block.hasUserInput && block.value.trim() !== ''

      // 第一次按backspace：如果有内容，先清空内容
      if (atStart && hasContent) {
        e.preventDefault()                  // 阻止默认删除行为
        clearHighlightBlock(currentIndex)   // 清空高亮块内容
        return
      }

      // 第二次按backspace（内容已清空）：删除整个高亮区域
      if (atStart && isEmpty) {
        e.preventDefault()                  // 阻止默认删除行为
        deleteHighlightBlock(currentIndex)  // 删除整个高亮块
        return
      }
    }
  }

  // 对于普通文本区域，使用默认的backspace行为
  // 但如果在开头且为空，可以跳转到前一个组件
  if (block.type === 'text') {
    if (sel && sel.rangeCount > 0) {
      const range = sel.getRangeAt(0)       // 获取选择范围
      const atStart = range.startOffset === 0  // 判断光标是否在开头
      const isEmpty = el.innerText.trim() === ''  // 判断内容是否为空

      if (atStart && isEmpty) {
        e.preventDefault()                  // 阻止默认删除行为
        jumpToPreviousBlock(currentIndex)   // 跳转到前一个块
        return
      }
    }
  }

  // 其他情况使用浏览器默认的backspace行为
}

/**
 * 处理delete键事件
 * 与backspace键类似，支持高亮区域的渐进式删除，但从结尾开始
 * @param {KeyboardEvent} e - 键盘事件对象
 * @param {number} currentIndex - 当前块的索引位置
 */
function handleDeleteKey(e, currentIndex) {
  const el = e.target                       // 获取当前DOM元素
  const sel = window.getSelection()         // 获取当前选择对象
  const block = blocks.value[currentIndex]  // 获取当前块数据

  // 如果是高亮区域，实现特殊的删除逻辑
  if (block.type === 'highlight') {
    // 检查是否在高亮区域的结尾位置
    if (sel && sel.rangeCount > 0) {
      const range = sel.getRangeAt(0)       // 获取选择范围
      const atEnd = range.startOffset === el.innerText.length  // 判断光标是否在结尾
      // 判断内容是否为空（空字符串或只有占位符）
      const isEmpty = el.innerText.trim() === '' ||
        el.innerText === block.placeholder
      // 判断是否有实际的用户输入内容
      const hasContent = block.hasUserInput && block.value.trim() !== ''

      // 第一次按delete：如果有内容，先清空内容
      if (atEnd && hasContent) {
        e.preventDefault()                  // 阻止默认删除行为
        clearHighlightBlock(currentIndex)   // 清空高亮块内容
        return
      }

      // 第二次按delete（内容已清空）：删除整个高亮区域
      if (atEnd && isEmpty) {
        e.preventDefault()                  // 阻止默认删除行为
        deleteHighlightBlock(currentIndex)  // 删除整个高亮块
        return
      }
    }
  }

  // 对于普通文本区域，使用默认的delete行为
  // 但如果在结尾且为空，可以跳转到下一个组件
  if (block.type === 'text') {
    if (sel && sel.rangeCount > 0) {
      const range = sel.getRangeAt(0)       // 获取选择范围
      const atEnd = range.startOffset === el.innerText.length  // 判断光标是否在结尾
      const isEmpty = el.innerText.trim() === ''  // 判断内容是否为空

      if (atEnd && isEmpty && currentIndex < blocks.value.length - 1) {
        e.preventDefault()                  // 阻止默认删除行为
        focusBlock(currentIndex + 1, 'start')  // 跳转到下一个块的开头
        return
      }
    }
  }

  // 其他情况使用浏览器默认的delete行为
}
// ==================== 高亮块操作函数 ====================
/**
 * 清空高亮区域内容
 * 将高亮块的内容清空，恢复到占位符状态，但不删除块本身
 * @param {number} index - 高亮区域在blocks数组中的索引位置
 */
function clearHighlightBlock(index) {
  const block = blocks.value[index]         // 获取指定的块数据
  if (block.type === 'highlight') {
    block.value = ''                        // 清空用户输入的值
    block.hasUserInput = false              // 标记为没有用户输入
    block.displayValue = block.placeholder  // 恢复显示占位符
  }

  emitChangeEvent()                         // 触发变化事件，更新v-model
}

/**
 * 删除整个高亮区域
 * 完全移除高亮块，并合并相邻的文本块，然后重新聚焦
 * @param {number} index - 要删除的高亮区域索引位置
 */
function deleteHighlightBlock(index) {
  const block = blocks.value[index]         // 获取指定的块数据
  if (block.type === 'highlight') {
    // 从blocks数组中删除该块
    blocks.value.splice(index, 1)

    // 同步更新DOM引用数组，保持索引一致性
    blockRefs.value.splice(index, 1)

    // 合并删除后可能相邻的文本块
    mergeAdjacentTextBlocks()

    // 等待DOM更新后，聚焦到合适的位置
    nextTick(() => {
      const targetIndex = Math.max(0, index - 1)  // 计算目标索引（不小于0）
      if (targetIndex < blocks.value.length) {
        focusBlock(targetIndex, 'end')      // 聚焦到前一个块的末尾
      }
    })

    emitChangeEvent()                       // 触发变化事件，更新v-model
  }
}

/**
 * 合并相邻的文本块
 * 当删除高亮块后，可能会产生相邻的文本块，需要将它们合并为一个块
 * 使用倒序遍历避免索引变化问题
 */
function mergeAdjacentTextBlocks() {
  // 从后往前遍历，避免删除元素时索引变化的问题
  for (let i = blocks.value.length - 1; i > 0; i--) {
    const currentBlock = blocks.value[i]     // 当前块
    const prevBlock = blocks.value[i - 1]    // 前一个块

    // 如果当前块和前一个块都是文本块，则合并它们
    if (currentBlock.type === 'text' && prevBlock.type === 'text') {
      prevBlock.value += currentBlock.value  // 将当前块的内容追加到前一个块
      blocks.value.splice(i, 1)             // 删除当前块
      blockRefs.value.splice(i, 1)          // 同步删除DOM引用
    }
  }
}

/**
 * 跳转到前一个组件块
 * 用于键盘导航时向前跳转
 * @param {number} currentIndex - 当前块的索引位置
 */
function jumpToPreviousBlock(currentIndex) {
  if (currentIndex > 0) {                   // 确保不是第一个块
    focusBlock(currentIndex - 1, 'end')     // 聚焦到前一个块的末尾
  }
}

// ==================== 容器交互处理 ====================
/**
 * 处理容器点击事件
 * 当用户点击输入框的空白区域时，聚焦到最后一个块
 * @param {MouseEvent} e - 鼠标点击事件对象
 */
function handleContainerClick(e) {
  // 检查点击的是否是容器本身（而不是子元素）
  if (e.target === e.currentTarget) {
    e.preventDefault()                      // 阻止默认行为
    const lastIndex = blocks.value.length - 1  // 计算最后一个块的索引
    if (lastIndex >= 0) {                   // 确保至少有一个块
      nextTick(() => {                      // 等待DOM更新
        focusBlock(lastIndex, 'end')        // 聚焦到最后一个块的末尾
      })
    }
  }
}

/**
 * 处理placeholder点击事件
 * 当用户点击placeholder时，聚焦到第一个可编辑区域
 */
function handlePlaceholderClick() {
  // 如果没有blocks，先初始化
  if (blocks.value.length === 0) {
    initializeComponent()
  }

  // 聚焦到第一个高亮区域，如果没有则聚焦到第一个块
  nextTick(() => {
    const firstHighlightIndex = blocks.value.findIndex(block => block.type === 'highlight')
    if (firstHighlightIndex !== -1) {
      focusBlock(firstHighlightIndex, 'start')
    } else if (blocks.value.length > 0) {
      focusBlock(0, 'start')
    }
  })
}

/**
 * 全选所有文本内容
 * 创建一个跨越所有块的选择范围，实现真正的全选功能
 */
function selectAllText() {
  if (!containerRef.value) return         // 确保容器引用存在

  const selection = window.getSelection() // 获取选择对象
  if (!selection) return                  // 确保选择对象存在

  selection.removeAllRanges()            // 清除现有的所有选择范围

  const range = document.createRange()   // 创建新的选择范围
  range.selectNodeContents(containerRef.value)  // 选择容器的所有内容
  selection.addRange(range)             // 添加选择范围到选择对象


}

/**
 * 手动插入换行符
 * 用于Ctrl+Enter和Shift+Enter的换行功能
 * @param {HTMLElement} element - 目标元素
 */
function insertLineBreak(element) {
  if (!element) return

  const selection = window.getSelection()
  if (!selection || selection.rangeCount === 0) return

  const range = selection.getRangeAt(0)

  // 创建换行符节点
  const br = document.createElement('br')

  // 删除选中的内容（如果有）
  range.deleteContents()

  // 插入换行符
  range.insertNode(br)

  // 将光标移动到换行符后面
  range.setStartAfter(br)
  range.setEndAfter(br)
  selection.removeAllRanges()
  selection.addRange(range)

  // 触发输入事件，更新组件状态
  const inputEvent = new Event('input', { bubbles: true })
  element.dispatchEvent(inputEvent)
}

/**
 * 处理复制文本操作
 * 当用户按Ctrl+C时，复制完整的文本内容到剪贴板
 */
function handleCopyText() {
  const fullText = getFullText()            // 获取完整的文本内容

  // 尝试使用现代的Clipboard API
  if (navigator.clipboard && navigator.clipboard.writeText) {
    navigator.clipboard.writeText(fullText).catch(() => {
      // 如果现代API失败，回退到传统方法
      fallbackCopyText(fullText)
    })
  } else {
    // 浏览器不支持现代API，使用传统方法
    fallbackCopyText(fullText)
  }
}

/**
 * 传统的复制文本方法（兼容旧浏览器）
 * @param {string} text - 要复制的文本
 */
function fallbackCopyText(text) {
  // 创建临时的textarea元素
  const textarea = document.createElement('textarea')
  textarea.value = text                     // 设置要复制的文本
  textarea.style.position = 'fixed'        // 固定定位，避免影响布局
  textarea.style.opacity = '0'             // 透明，用户看不到
  document.body.appendChild(textarea)      // 添加到DOM

  textarea.select()                        // 选择文本
  textarea.setSelectionRange(0, 99999)     // 确保选择所有文本

  try {
    // 使用已弃用但兼容性好的execCommand作为回退方案
    // eslint-disable-next-line deprecation/deprecation
    document.execCommand('copy')  // 执行复制命令
  } catch (err) {
    // 静默处理错误，避免控制台噪音
  }

  document.body.removeChild(textarea)      // 清理临时元素
}

// ==================== 文本处理和事件发射 ====================
/**
 * 处理表单提交或其他自定义逻辑
 * 当用户在最后一个块按回车键时触发
 */
function handleFormSubmit() {
  const fullText = getFullText()             // 获取完整的文本内容

  // 构建详细的块信息
  const textBlocks = []
  const highlightBlocks = []

  blocks.value.forEach((block, index) => {
    if (block.type === 'text') {
      textBlocks.push({ index, value: block.value })
    } else {
      highlightBlocks.push({
        index,
        value: block.value,
        placeholder: block.placeholder,
        hasUserInput: block.hasUserInput
      })
    }
  })

  // 触发提交事件，父组件可以监听此事件
  emit('submit', {
    fullText,                               // 完整文本
    textBlocks,                             // 文本块数组
    highlightBlocks,                        // 高亮块数组
    totalBlocks: blocks.value.length        // 总块数
  })

  // 同时触发回车事件（向后兼容）
  emit('enter', fullText)
}

/**
 * 获取完整的文本内容
 * 根据keepBrackets属性决定是否保留中括号
 * @returns {string} 拼接后的完整文本内容
 */
function getFullText() {
  let result = ''                           // 结果字符串

  blocks.value.forEach(block => {
    if (block.type === 'text') {
      // 普通文本块：直接追加内容
      result += block.value
    } else {
      // 高亮块：根据用户输入状态选择内容
      const content = block.hasUserInput ? block.value : block.placeholder
      if (props.keepBrackets) {
        result += `[${content}]`            // 保留中括号格式
      } else {
        result += content                   // 去除中括号，只保留内容
      }
    }
  })

  return result                             // 返回拼接后的完整文本
}

/**
 * 发射内容变化事件
 * 当组件内容发生变化时，更新v-model并触发change事件
 * 这是组件与外部通信的核心函数
 */
function emitChangeEvent() {
  const fullText = getFullText()             // 获取当前完整文本

  // 发射v-model更新事件，实现双向绑定
  emit('update:modelValue', fullText)

  // 构建详细的块信息，供父组件使用
  const textBlocks = []                      // 存储所有文本块信息
  const highlightBlocks = []                 // 存储所有高亮块信息

  blocks.value.forEach((block, index) => {
    if (block.type === 'text') {
      // 收集文本块信息
      textBlocks.push({ index, value: block.value })
    } else {
      // 收集高亮块信息
      highlightBlocks.push({
        index,                              // 块在数组中的位置
        value: block.value,                 // 用户输入的值
        placeholder: block.placeholder,     // 占位符文本
        hasUserInput: block.hasUserInput    // 是否有用户输入
      })
    }
  })

  // 发射详细的change事件，包含所有必要信息
  emit('change', {
    fullText,                               // 完整的文本内容
    textBlocks,                             // 文本块数组
    highlightBlocks,                        // 高亮块数组
    totalBlocks: blocks.value.length        // 总块数
  })
}

/**
 * 确保光标在元素中可见
 * 特别处理空元素或光标在文本开头的情况
 * @param {HTMLElement} element - 目标元素
 */
function ensureCursorVisible(element) {
  if (!element) return

  try {
    const sel = window.getSelection()
    if (!sel || sel.rangeCount === 0) return

    const range = sel.getRangeAt(0)

    // 如果元素为空，确保有内容来显示光标
    if (element.textContent.length === 0) {
      // 临时插入一个零宽度空格来确保光标可见
      const textNode = document.createTextNode('\u200B')
      element.appendChild(textNode)

      // 设置光标到零宽度空格之前
      range.setStart(textNode, 0)
      range.setEnd(textNode, 0)
      sel.removeAllRanges()
      sel.addRange(range)
    } else if (range.startContainer === element && range.startOffset === 0) {
      // 如果光标在元素开头，确保光标位置正确
      const firstChild = element.firstChild
      if (firstChild && firstChild.nodeType === Node.TEXT_NODE) {
        range.setStart(firstChild, 0)
        range.setEnd(firstChild, 0)
        sel.removeAllRanges()
        sel.addRange(range)
      }
    }

    // 滚动到光标位置（如果需要）
    element.scrollIntoView({ block: 'nearest', inline: 'nearest' })
  } catch (error) {
    // 静默处理错误
  }
}

// ==================== 光标位置检测函数 ====================
/**
 * 检测光标是否在元素的开头位置
 * @param {HTMLElement} element - 目标元素
 * @param {Range} range - 选择范围
 * @returns {boolean} 是否在开头
 */
function isCaretAtStart(element, range) {
  if (!element || !range) return false

  // 检查range是否在元素的开头
  if (range.startOffset === 0) {
    // 如果元素有子节点，检查是否在第一个文本节点的开头
    if (element.childNodes.length > 0) {
      const firstNode = element.childNodes[0]
      return range.startContainer === firstNode || range.startContainer === element
    } else {
      // 空元素，检查是否在元素本身的开头
      return range.startContainer === element
    }
  }

  return false
}

/**
 * 检测光标是否在元素的结尾位置
 * @param {HTMLElement} element - 目标元素
 * @param {Range} range - 选择范围
 * @returns {boolean} 是否在结尾
 */
function isCaretAtEnd(element, range) {
  if (!element || !range) return false

  const textContent = element.textContent || ''
  const textLength = textContent.length

  // 如果元素为空，光标在元素内就是结尾
  if (textLength === 0) {
    return range.startContainer === element
  }

  // 检查光标是否在文本内容的结尾
  if (element.childNodes.length > 0) {
    const lastNode = element.childNodes[element.childNodes.length - 1]
    if (lastNode.nodeType === Node.TEXT_NODE) {
      return range.startContainer === lastNode && range.startOffset === lastNode.textContent.length
    } else {
      // 如果最后一个节点不是文本节点（比如<br>），检查光标是否在元素结尾
      return range.startContainer === element && range.startOffset === element.childNodes.length
    }
  } else {
    // 没有子节点，检查是否在元素结尾
    return range.startContainer === element && range.startOffset === textLength
  }
}

// ==================== 焦点控制函数 ====================
/**
 * 聚焦指定的组件块
 * 用于键盘导航和程序化的焦点控制
 * @param {number} index - 要聚焦的组件块索引
 * @param {string} pos - 光标位置：'start'表示开头，'end'表示结尾
 */
function focusBlock(index, pos) {
  // 使用setTimeout确保在所有事件处理完成后执行
  setTimeout(() => {
    const targetRef = blockRefs.value[index]  // 获取目标DOM元素

    if (targetRef && document.contains(targetRef)) {  // 确保元素存在且在DOM中
      targetRef.focus()                     // 设置焦点到目标元素

      // 等待焦点设置完成后再设置光标位置
      nextTick(() => {
        if (pos === 'start') {
          setCursorToStart(targetRef)       // 将光标设置到开头
        } else {
          setCursorToPos(targetRef, 'end')  // 将光标设置到结尾
        }
      })
    }
  }, 10) // 短暂延迟确保DOM状态稳定
}

// ==================== 组件初始化 ====================
/**
 * 初始化组件数据
 * 解析预设文本或v-model值，生成blocks数组，并保留用户已输入的内容
 * 这个函数在组件挂载和数据变化时被调用
 */
function initializeComponent() {
  // 第一步：保存当前用户输入状态，防止重新初始化时丢失用户数据
  const currentUserInputs = new Map()      // 使用Map存储用户输入状态
  if (blocks.value.length > 0) {           // 如果已有块数据
    blocks.value.forEach((block, index) => {
      // 只保存有用户输入的高亮块状态
      if (block.type === 'highlight' && block.hasUserInput) {
        currentUserInputs.set(index, {
          value: block.value,               // 用户输入的值
          hasUserInput: block.hasUserInput  // 用户输入状态
        })
      }
    })
  }

  // 第二步：根据props决定使用哪个文本源进行初始化
  if (!props.modelValue && props.presetText) {
    // 如果v-model为空但有预设文本，使用预设文本初始化
    emit('update:modelValue', props.presetText)  // 将预设文本赋值给v-model
    blocks.value = parsePresetText(props.presetText)  // 解析预设文本
  } else {
    // 使用v-model的值或预设文本作为数据源
    const textToUse = props.modelValue || props.presetText
    blocks.value = parsePresetText(textToUse)    // 解析文本生成块数组

    // 如果有v-model内容，需要解析其中可能包含的用户输入
    if (props.modelValue) {
      parseModelValue(props.modelValue)     // 解析v-model中的用户输入内容
    }
  }

  // 第三步：恢复之前保存的用户输入状态
  if (currentUserInputs.size > 0) {        // 如果有保存的用户输入
    blocks.value.forEach((block, index) => {
      // 恢复对应位置的高亮块的用户输入状态
      if (block.type === 'highlight' && currentUserInputs.has(index)) {
        const savedInput = currentUserInputs.get(index)
        block.value = savedInput.value      // 恢复用户输入的值
        block.hasUserInput = savedInput.hasUserInput  // 恢复输入状态
        block.displayValue = savedInput.value  // 恢复显示值
      }
    })
  }

  // 第四步：重置DOM引用数组，等待新的DOM元素挂载
  blockRefs.value = []
}

/**
 * 解析modelValue中的用户输入内容
 * 当v-model有值时，解析其中的中括号内容，识别哪些是用户输入的内容
 * @param {string} modelValue - v-model绑定的值，可能包含用户已输入的内容
 */
function parseModelValue(modelValue) {
  // 使用正则表达式匹配所有中括号及其内容
  const regex = /\[([^\]]*)\]/g            // 匹配 [内容] 格式
  let match                                // 当前匹配结果
  let highlightIndex = 0                   // 高亮块索引计数器

  // 循环查找所有中括号匹配
  while ((match = regex.exec(modelValue)) !== null) {
    // 获取当前所有高亮块
    const highlightBlocks = blocks.value.filter(block => block.type === 'highlight')
    if (highlightIndex < highlightBlocks.length) {
      const block = highlightBlocks[highlightIndex]  // 获取对应的高亮块
      const content = match[1]              // 提取中括号内的内容

      // 判断内容是否为用户输入（不等于占位符且不为空）
      if (content !== block.placeholder && content.trim() !== '') {
        block.value = content               // 设置用户输入的值
        block.hasUserInput = true           // 标记为有用户输入
        block.displayValue = content        // 更新显示值
      } else {
        // 如果内容等于占位符或为空，保持原有状态不变
        block.displayValue = block.hasUserInput ? block.value : block.placeholder
      }
    }
    highlightIndex++                        // 移动到下一个高亮块
  }
}

// ==================== 生命周期钩子 ====================
/**
 * 组件挂载后的初始化操作
 * 初始化组件数据并设置初始焦点
 */
onMounted(() => {
  // 初始化组件数据结构
  initializeComponent()

  // 等待DOM完全渲染后设置焦点
  nextTick(() => {
    // 再等待一个tick确保所有ref都设置完成
    nextTick(() => {
      focusFirstHighlight()
    })
  })
})

// ==================== 响应式监听器 ====================
/**
 * 监听modelValue变化，实现外部数据到组件的同步
 * 当父组件修改v-model值时，组件内容会相应更新
 */
watch(() => props.modelValue, (newValue) => {
  if (newValue !== undefined) {
    // 避免循环更新：只有当新值与当前生成的文本不同时才更新
    // 这样可以防止组件内部变化触发的更新造成无限循环
    const currentText = getFullText()
    if (newValue !== currentText) {
      initializeComponent()             // 重新初始化组件以反映新的值
    }
  }
}, { immediate: false })                // 不立即执行，避免初始化时的重复调用

/**
 * 监听preset-text变化，处理模板更新
 * 当预设文本变化时，智能决定是否更新组件内容
 */
watch(() => props.presetText, (newPresetText) => {
  if (newPresetText) {
    // 检查当前是否有用户输入，避免覆盖用户数据
    const hasUserInput = blocks.value.some(block =>
      block.type === 'highlight' && block.hasUserInput
    )

    if (!props.modelValue && !hasUserInput) {
      // 只有在v-model为空且没有用户输入时，才用新的预设文本更新v-model
      // 这样可以避免意外覆盖用户已输入的内容
      emit('update:modelValue', newPresetText)
    }

    // 总是重新初始化以更新模板结构，但会保留用户输入的内容
    initializeComponent()
  }
}, { immediate: false })                // 不立即执行，避免初始化时的重复调用
</script>

<style scoped>
/* ==================== 主容器样式 ==================== */
/* 高亮文本输入框的主容器，包含所有文本块和高亮块 */
.highlight-input {
  width: 100%;
  /* 占满父容器宽度 */
  min-height: 40px;
  /* 最小高度，确保有足够的点击区域 */
  border: 1px solid #dcdfe6;
  /* 边框已注释，根据需要启用 */
  border-radius: 6px;
  /* 圆角边框，现代化外观 */
  padding: 8px 12px;
  /* 内边距，提供舒适的输入空间 */
  background: #fff;
  /* 白色背景 */
  font-size: 16px;
  /* 字体大小，适合阅读和输入 */
  line-height: 1.8;
  /* 行高，提供良好的文本间距 */
  cursor: text;
  /* 鼠标悬停时显示文本光标 */
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  /* 系统字体栈 */
  text-align: left;
  /* 左对齐文本 */
  word-break: break-all;
  /* 允许在任意字符间换行 */
  word-wrap: break-word;
  /* 长单词自动换行 */
  overflow-wrap: break-word;
  /* 现代浏览器的换行属性 */
  position: relative;
  /* 相对定位，为子元素提供定位上下文 */
  white-space: pre-wrap;
  /* 保留空白字符并允许换行 */
  box-sizing: border-box;
  /* 边框盒模型，padding和border包含在width内 */
}

/* 扩展点击区域的伪元素 */
.highlight-input::after {
  content: '';
  /* 空内容的伪元素 */
  flex: 1;
  /* 占据剩余空间 */
  min-height: 1px;
  /* 最小高度，确保伪元素存在 */
}

/* 容器悬停状态 - 已注释，根据需要启用 */
.highlight-input:hover {
  border-color: #c0c4cc;
  /* 悬停时的边框颜色 */
}

/* 容器内有元素聚焦时的状态 */
.highlight-input:focus-within {
  /* border-color: #409eff; */
  /* 聚焦时的边框颜色已注释 */
  /* box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2); */
  /* 聚焦时的外发光效果 */
}

/* ==================== 普通文本块样式 ==================== */
/* 普通文本块的样式，用于显示和编辑常规文本内容 */
.normal-text[contenteditable] {
  outline: none;
  /* 移除聚焦时的默认轮廓 */
  color: #333;
  /* 深灰色文字，易于阅读 */
  background: transparent;
  /* 透明背景，与容器背景一致 */
  margin: 0;
  /* 移除默认外边距 */
  border: none;
  /* 移除边框 */
  white-space: pre-wrap;
  /* 保留空白字符并允许换行 */
  word-break: break-all;
  /* 允许在任意字符间换行 */
  word-wrap: break-word;
  /* 长单词自动换行 */
  overflow-wrap: break-word;
  /* 现代浏览器的换行属性 */
  hyphens: auto;
  /* 自动连字符 */
  text-align: left;
  /* 左对齐文本 */
  display: inline;
  /* 内联显示，支持文本流换行 */
  vertical-align: baseline;
  /* 基线对齐 */
  caret-color: #333;
  /* 光标颜色与文字颜色一致 */
  min-width: 1px;
  /* 最小宽度，确保空文本块也能显示光标 */
  min-height: 1.2em;
  /* 最小高度，确保有足够的光标显示空间 */
}

/* 防止空的普通文本块塌陷 */
.normal-text[contenteditable]:empty:before {
  content: '\200B';
  /* 零宽度空格，确保空元素有高度 */
}

/* 确保普通文本块聚焦时光标可见 */
.normal-text[contenteditable]:focus {
  position: relative;
  /* 相对定位，确保光标层级正确 */
  z-index: 1;
  /* 提高层级，确保光标在其他元素之上 */
}

/* 特殊处理：高亮块后的普通文本块 */
.normal-text.after-highlight[contenteditable] {
  margin-left: 2px;
  /* 添加微小间距，确保光标有显示空间 */
  position: relative;
  /* 相对定位，确保光标层级正确 */
}

/* 为高亮块后的普通文本块添加额外的光标可见性保证 */
.normal-text.after-highlight[contenteditable]:focus {
  /* background: rgba(64, 158, 255, 0.05); */
  /* 极淡的背景色，帮助显示光标位置 */
}

/* 确保光标在文本开头时可见 */
.normal-text[contenteditable]:focus:empty:after {
  content: '';
  /* 空内容 */
  display: inline-block;
  /* 内联块显示 */
  width: 1px;
  /* 1像素宽度 */
  height: 1em;
  /* 与文字高度一致 */
  background: transparent;
  /* 透明背景 */
}

/* ==================== 整体占位符样式 ==================== */
/* 当输入框为空时显示的占位符 */
.input-placeholder {
  position: absolute;
  /* 绝对定位，覆盖在输入框上 */
  top: 8px;
  /* 与容器padding保持一致 */
  left: 12px;
  /* 与容器padding保持一致 */
  right: 12px;
  /* 与容器padding保持一致 */
  color: #999;
  /* 灰色文字，表示占位符 */
  font-size: 16px;
  /* 与输入框字体大小一致 */
  line-height: 1.8;
  /* 与输入框行高一致 */
  pointer-events: auto;
  /* 允许点击事件 */
  cursor: text;
  /* 文本光标 */
  user-select: none;
  /* 禁止选择占位符文本 */
  z-index: 1;
  /* 确保在输入内容之上 */
}

/* 输入框有内容时隐藏占位符 */
.highlight-input:not(.is-empty) .input-placeholder {
  display: none;
  /* 有内容时隐藏占位符 */
}

/* ==================== 高亮区域块样式 ==================== */
/* 高亮区域的样式，用于显示需要用户填写的特殊区域 */
.highlight-block[contenteditable] {
  outline: none;
  /* 移除聚焦时的默认轮廓 */
  min-width: 2em;
  /* 最小宽度，确保空区域可见 */
  min-height: 1.2em;
  /* 最小高度，确保有足够的点击区域 */
  margin: 0;
  /* 移除默认外边距 */
  padding: 2px 6px;
  /* 内边距，提供舒适的输入空间 */
  border-radius: 4px;
  /* 圆角，现代化外观 */
  background: #f0f9ff;
  /* 浅蓝色背景，突出显示 */
  color: #409eff;
  /* 蓝色文字，与背景协调 */
  font-weight: 500;
  /* 中等字重，增强可读性 */
  /* box-shadow: 0 0 0 1px #b3d8ff inset; */
  /* 内阴影已注释，根据需要启用 */
  position: relative;
  /* 相对定位，为伪元素提供上下文 */
  border: none;
  /* 移除边框 */
  white-space: pre-wrap;
  /* 保留空白字符并允许换行 */
  word-break: break-all;
  /* 允许在任意字符间换行 */
  word-wrap: break-word;
  /* 长单词自动换行 */
  overflow-wrap: break-word;
  /* 现代浏览器的换行属性 */
  hyphens: auto;
  /* 自动连字符 */
  text-align: left;
  /* 左对齐文本 */
  display: inline-block;
  /* 内联块显示，支持设置宽高 */
  vertical-align: top;
  /* 顶部对齐，与文本基线对齐 */
  caret-color: #409eff;
  /* 光标颜色与文字颜色一致 */
  max-width: 100%;
  /* 最大宽度不超过容器 */
  box-sizing: border-box;
  /* 边框盒模型 */
}

/* 高亮区域聚焦状态 */
.highlight-block[contenteditable]:focus {
  /* background: #e6f7ff; */
  /* 聚焦时的背景色，比默认稍深 */
  /* box-shadow: 0 0 0 2px #409eff inset; */
  /* 内阴影已注释 */
  /* border-color: #409eff; */
  /* 边框颜色已注释 */
}

/* ==================== 用户输入状态样式 ==================== */
/* 当高亮区域有用户输入内容时的样式（文字加深效果） */
.highlight-block.has-content {
  color: #1760a0;
  /* 更深的蓝色，突出用户输入 */
  font-weight: 700;
  /* 加粗字体，增强视觉效果 */
  background: #f0f9ff;
  /* 保持背景色一致 */
}

/* 有用户输入内容的高亮区域聚焦时的样式 */
.highlight-block.has-content:focus {
  color: #1760a0;
  /* 保持深蓝色 */
  background: #e6f7ff;
  /* 聚焦时的背景色 */
}

/* ==================== 占位符样式 ==================== */
/* 高亮区域为空时显示占位符文本 */
.highlight-block[contenteditable]:empty:before {
  content: attr(data-placeholder);
  /* 从data-placeholder属性获取占位符文本 */
  /* font-weight: 400; */
  /* 字重已注释，使用默认 */
  pointer-events: none;
  /* 禁用鼠标事件，避免干扰输入 */
}

/* 聚焦时占位符的透明度效果 */
.highlight-block[contenteditable]:focus:empty:before {
  opacity: 0.3;
  /* 聚焦时占位符变淡，提示用户可以输入 */
}

/* ==================== 布局优化 ==================== */
/* 防止文本块不必要的收缩 */
.highlight-input>span {
  flex-shrink: 0;
  /* 禁止收缩，保持原始大小 */
}

/* 最后一个元素的特殊处理 */
.highlight-input>span:last-child {
  flex-shrink: 1;
  /* 允许最后一个元素收缩 */
  min-width: auto;
  /* 移除最小宽度限制 */
}

/* ==================== 响应式设计 ==================== */
/* 移动端适配 */
@media (max-width: 768px) {
  .highlight-input {
    font-size: 14px;
    /* 移动端使用较小字体 */
    padding: 6px 10px;
    /* 减少内边距，节省空间 */
  }

  /* 移动端高亮区域样式调整 */
  .highlight-block[contenteditable] {
    padding: 1px 4px;
    /* 减少内边距，适应小屏幕 */
    min-width: 1.5em;
    /* 减少最小宽度 */
  }

  /* 移动端允许更灵活的换行 */
  .highlight-input>span {
    flex-shrink: 1;
    /* 允许所有元素收缩，提高空间利用率 */
  }
}

/* ==================== 无障碍支持 ==================== */
/* 为聚焦的高亮区域提供无障碍轮廓 */
.highlight-block[contenteditable]:focus {
  outline: 2px solid transparent;
  /* 透明轮廓，不影响视觉但支持屏幕阅读器 */
  outline-offset: 2px;
  /* 轮廓偏移，避免与内容重叠 */
}

/* ==================== 高对比度模式支持 ==================== */
/* 为视觉障碍用户提供更好的对比度 */
@media (prefers-contrast: high) {
  .highlight-block[contenteditable] {
    border: 2px solid #409eff;
    /* 高对比度模式下添加明显边框 */
    /* background: #ffffff; */
    /* 纯白背景，提高对比度 */
  }

  /* 有用户输入内容时的高对比度样式 */
  .highlight-block.has-content {
    /* background: #f0f9ff; */
    /* 浅色背景，保持可读性 */
    border-color: #1760a0;
    /* 深色边框，增强对比 */
  }
}
</style>